﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace SimpleData.Infrastructure.Utils
{
	/// <summary>
	/// Retorna a propriedade definida em uma expressão LINQ
	/// </summary>
	public sealed class PropertyNameExpressionResolver : ExpressionVisitor
	{
		private string propertyName = null;

		/// <summary>
		/// 
		/// </summary>
		public PropertyNameExpressionResolver( Expression expression )
			: this( new StringBuilder(), expression )
		{ }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="propertyNameBuilder"></param>
		public PropertyNameExpressionResolver( StringBuilder propertyNameBuilder, Expression expression )
		{
			this.Builder = propertyNameBuilder;
			this.Expression = expression;
			this.Includes = PropertyNameIncludes.OnlyProperties;
		}

		/// <summary>
		/// Obtém o construtor da string com o nome da propriedade
		/// </summary>
		public StringBuilder Builder
		{
			get;
			private set;
		}

		/// <summary>
		/// Obtém a expressão LINQ com a propriedade
		/// </summary>
		public Expression Expression
		{
			get;
			private set;
		}

		/// <summary>
		/// Define o que será retornado
		/// </summary>
		public PropertyNameIncludes Includes
		{ get; set; }

		/// <summary>
		/// Obtém o nome da propriedade
		/// </summary>
		public string PropertyName
		{
			get
			{
				if ( this.propertyName == null )
				{
					this.Visit( this.Expression );
					this.propertyName = this.Builder.ToString();
				}
				return this.propertyName;
			}
		}

		/// <summary>
		/// Redefine uma nova expressão para ser resolvida
		/// </summary>
		/// <param name="expression"></param>
		public void SetExpression( Expression expression )
		{
			this.Expression = expression;
			this.Builder = new StringBuilder();
		}

		public override Expression Visit( Expression node )
		{
			return base.Visit( node );
		}

		protected override Expression VisitLambda<T>( Expression<T> node )
		{
			return this.Visit( node.Body );
//			return base.VisitLambda<T>( node );
		}

		protected override Expression VisitBinary( BinaryExpression node )
		{
			//var exp = base.VisitBinary( node ); 

			if ( node.NodeType == ExpressionType.ArrayIndex
				&& ( this.Includes & PropertyNameIncludes.PropertiesAndExternals ) == PropertyNameIncludes.PropertiesAndExternals )
			{
				base.Visit( node.Left );

				this.Builder.Append( '[' );
				base.Visit( node.Right );
				this.Builder.Append( ']' );

				return node;
			}
			else
				return base.VisitBinary( node );
		}

		protected override Expression VisitParameter( ParameterExpression node )
		{
			if ( ( this.Includes & PropertyNameIncludes.PropertiesAndInputParameter ) == PropertyNameIncludes.PropertiesAndInputParameter )
			{
				// Adiciona o nome do parâmetro
				if ( this.Builder.Length > 0 )
					this.Builder.Append( '.' );
				this.Builder.Append( node.Name );
			}
			return base.VisitParameter( node );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		protected override Expression VisitMember( MemberExpression node )
		{
			// Primeiro visita os outros nodes
			var exp = base.VisitMember( node );

			// Inclui o nome da propriedade (se for uma propriedade ou field)
			if ( node.NodeType == ExpressionType.MemberAccess )
			{
				if ( node.Member.MemberType == MemberTypes.Property
					|| node.Member.MemberType == MemberTypes.Field )
				{
					if ( ( node.Type.IsValueType || node.Type.Equals( typeof( string ) ) )
						&& node.Has( ExpressionType.Constant ) )
					{
						if ( ( this.Includes & PropertyNameIncludes.PropertiesAndExternals ) == PropertyNameIncludes.PropertiesAndExternals )
						{
							object value = node.GetValue();
							this.Builder.Append( value );
						}
					}
					else
					{
						if ( this.Builder.Length > 0 )
							this.Builder.Append( '.' );
						this.Builder.Append( node.Member.Name );
					}
				}
			}

			return exp;
		}
	}
}
